﻿using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using Bookvar.ApplicationModel;
using Bookvar.ObjectModel;
using Bookvar.ObjectModel.Actions;
using Bookvar.ServiceModel;
using LocalizationResources = Bookvar.UI.Localization.Resources;

namespace Bookvar.UI.Actions
{
    /// <summary>
    /// This action adds a new topic with an image from the web. If the image thumbnail is already available
    /// it uses it until the image is downloaded asynchronously.
    /// </summary>
    /// If the TargetTopic property is set the image will be added to this topic. Otherwise a new topic will be created.
    /// The new topic will be added to the currently selected one.
    public class AddWebImageAction : ActionBase
    {
        private readonly ImageInfo imageInfo;
        private readonly Size imageSize;
        private ImageSource thumbnailSource;
        private string filePath;
        private Resource tempThumbnailResource;
        private readonly CompositeAction compositeAction;
        private Dispatcher uiDispatcher;

        /// <summary>
        /// Initializes a new instance of the <see cref="AddWebImageAction"/> class.
        /// </summary>
        /// <param name="imageInfo">The image info for the image that should be downloaded.</param>
        /// <param name="thumbnailSource">The <see cref="ImageSource"/> of the thumbnail.</param>
        /// <param name="imageSize">The desired image size.</param>
        /// <exception cref="ArgumentNullException">if <paramref name="imageInfo"/> or 
        /// <paramref name="thumbnailSource"/>is <c>null</c>.
        /// </exception>
        public AddWebImageAction( ImageInfo imageInfo, Size imageSize, ImageSource thumbnailSource )
        {
            if ( imageInfo == null ) throw new ArgumentNullException( "imageInfo" );
            if ( thumbnailSource == null ) throw new ArgumentNullException( "thumbnailSource" );

            this.imageInfo = imageInfo;
            this.thumbnailSource = thumbnailSource;
            this.imageSize = imageSize;

            compositeAction = new CompositeAction();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AddWebImageAction"/> class.
        /// </summary>
        /// <param name="imageInfo">The image info for the image that should be downloaded.</param>
        /// <param name="imageSize">The desired image size.</param>
        /// <param name="thumbnailSource">The <see cref="ImageSource"/> of the thumbnail.</param>
        /// <param name="target">The target topic.</param>
        /// <exception cref="ArgumentNullException">if <paramref name="imageInfo"/>, 
        /// <paramref name="thumbnailSource"/> or <paramref name="target"/> is <c>null</c>.</exception>
        public AddWebImageAction( ImageInfo imageInfo, Size imageSize, ImageSource thumbnailSource, Topic target )
            : this( imageInfo, imageSize, thumbnailSource )
        {
            if ( target == null ) throw new ArgumentNullException( "target" );

            TargetTopic = target;
        }

        /// <summary>
        /// Returns true, because this action supports redo.
        /// </summary>
        /// <value><c>true</c></value>
        public override bool CanRedo
        {
            get { return true; }
        }

        /// <summary>
        /// Returns true, because this action supports undo.
        /// </summary>
        /// <value><c>true</c></value>
        public override bool CanUndo
        {
            get { return true; }
        }

        private static IMindmapService mindmapService
        {
            get { return BookvarApplication.Current.ServiceLayer.GetService<IMindmapService>(); }
        }

        private static IErrorService ErrorService
        {
            get { return BookvarApplication.Current.ServiceLayer.GetService<IErrorService>(); }
        }

        /// <summary>
        /// Gets or sets the target topic. If this property is set the image will be added to this topic. 
        /// Otherwise a new topic will be created. The new topic will be added to the currently selected one.
        /// </summary>
        /// <value>The target topic.</value>
        public Topic TargetTopic { get; set; }

        /// <summary>
        /// Undoes this this action.
        /// </summary>
        public override void Undo()
        {
            base.Undo();

            compositeAction.Undo();
        }

        // TODO: Refactor to more actions and make this aciton a composite one.
        /// <summary>
        /// Executes this action. See <see cref="AddWebImageAction"/>.
        /// </summary>
        public override void Execute()
        {
            // we need the action state before it is changed and the base.Execute() changes the ActionState.
            // Consider changing action state AFTER Execute.
            ActionState originalActionState = State;

            base.Execute();

            // verify we are on the UI Thread.
            uiDispatcher = BookvarApplication.Current.Dispatcher;
            uiDispatcher.VerifyAccess();

            if ( originalActionState == ActionState.Undone )
            {
                compositeAction.Execute();
            }
            else
            {
                if ( TargetTopic == null )
                {
                    TargetTopic = new Topic { Name = imageInfo.Title };
                    Topic parent = mindmapService.SelectedTopic ?? mindmapService.CurrentMindmap.MainTopic;
                    var addTopicAction = new AddTopicAction( TargetTopic ) { Parent = parent };
                    addTopicAction.Execute();

                    compositeAction.Add( addTopicAction );
                    mindmapService.SelectedTopic = parent;
                }

                tempThumbnailResource = new Resource( imageInfo.Title, ResourceType.Image );
                tempThumbnailResource.ItemSource = thumbnailSource;
                tempThumbnailResource.VisualSize = new VisualSize( imageSize );

                TargetTopic.Resources.Add( tempThumbnailResource );
                ThreadPool.QueueUserWorkItem( LoadImageInThreadPool );
            }
        }

        /// <summary>
        /// Loads the image from the Live Service. This is done in another thread.
        /// </summary>
        /// <param name="asyncState">State of the async.</param>
        private void LoadImageInThreadPool( object asyncState )
        {
            try
            { 
                byte[] imageBytes = LoadImage( imageInfo );
                filePath = Path.GetTempFileName();
                File.WriteAllBytes( filePath, imageBytes );

                uiDispatcher.Invoke( DispatcherPriority.Normal, new Action( AddImageToMindMap ) );
            }
            catch ( WebException )
            {
                uiDispatcher.Invoke( DispatcherPriority.Normal, new Action( ShowErrorWhileLoadingImage ) );
            }
        }

        private static byte[] LoadImage( ImageInfo imageInfo )
        {
            using ( var webClient = new WebClient() )
            {
                byte[] result = webClient.DownloadData( imageInfo.ImageUri );
                return result;
            }
        }

        /// <summary>
        /// Adds the image to mind map in the UI thread.
        /// </summary>
        private void AddImageToMindMap()
        {
            Size newImageSize = imageSize;
            if ( TargetTopic.Resources.Contains( tempThumbnailResource ) )
            {
                newImageSize = tempThumbnailResource.VisualSize.ToWindowsSize();
                TargetTopic.Resources.Remove( tempThumbnailResource );
            }

            tempThumbnailResource = null;
            thumbnailSource = null;

            var imageResource = new Resource( imageInfo.Title, ResourceType.Image );
            imageResource.VisualSize = new VisualSize( newImageSize );

            IAction addResourceAction =
                TopicActions.CreateAddResourceAction(
                    imageResource, filePath, TargetTopic, mindmapService.CurrentMindmap );

            if ( State == ActionState.Executed || State == ActionState.New )
            {
                addResourceAction.Execute();
            }

            compositeAction.Add( addResourceAction );
        }

        private static void ShowErrorWhileLoadingImage()
        {
            ErrorService.ShowError( LocalizationResources.ErrorWhileLoadingImageFromLiveSearch );
        }
    }
}
